package lox.tool

import java.io.File
import java.io.PrintWriter


const val outputDir: String = "src"

fun main() {
    defineAST(
        outputDir, "Expr", listOf(
            "Assign   : Token name, Expr value",
            "Binary   : Expr left, Token operator, Expr right",
            "Call     : Expr callee, Token paren, List<Expr> arguments",
            "Get      : Expr object, Token name",
            "Grouping : Expr expression",
            "Literal  : Object value",
            "Logical  : Expr left, Token operator, Expr right",
            "Set      : Expr object, Token name, Expr value",
            "Super    : Token keyword, Token method",
            "This     : Token keyword",
            "Unary    : Token operator, Expr right",
            "Variable : Token name"
        ).map { it.replace("Object", "Any?").replace("object", "obj") })

    defineAST(
        outputDir, "Stmt", listOf(
            "Block      : List<Stmt> statements",
            "Class      : Token name, Expr.Variable? superclass, List<Stmt.Function> methods",
            "Expression : Expr expression",
            "Function   : Token name, List<Token> params, List<Stmt> body",
            "If         : Expr condition, Stmt thenBranch, Stmt? elseBranch",
            "Print      : Expr expression",
            "Return     : Token keyword, Expr? value",
            "While      : Expr condition, Stmt body",
            "Var        : Token name, Expr? initializer"
        ).map { it.replace("Object", "Any?") })

    println("Finished.")
}

fun defineAST(outputDir: String, baseName: String, types: List<String>) {
    val writer = File(outputDir, "$baseName.kt").run { PrintWriter(this, "UTF-8") }

    writer.println("package lox")
    writer.println()
    writer.println("abstract class $baseName { ")

    defineVisitor(writer, baseName, types)

    for (type in types) {
        type.split(':').run {
            val className = this[0].trim()
            val fields = this[1].trim()
            defineType(writer, baseName, className, fields)
        }
    }

    writer.println()
    writer.println("\tabstract fun <R> accept(visitor: Visitor<R>): R")

    writer.println("}")
    writer.close()
}

fun defineVisitor(writer: PrintWriter, baseName: String, types: List<String>) {
    writer.println("\tinterface Visitor<R> {")
    for (type in types) {
        type.split(":")[0].trim().run {
            writer.println("\t\tfun visit$this$baseName(${baseName.lowercase()}: $this): R")
        }
    }
    writer.println("\t}")
}

fun defineType(writer: PrintWriter, baseName: String, className: String, fields: String) {
    writer.println(
        "\tclass $className (${
            fields.run {
                this.split(',').joinToString {
                    "val ${it.trim().split(' ').reversed().joinToString(": ")}"
                }
            }
        }) : $baseName() {")

    writer.println("\t\toverride fun <R> accept(visitor: Visitor<R>): R = visitor.visit$className$baseName(this)")

    writer.println("\t}")
}